"======================================================================
"
" core.vim -
"
" Created by skywind on 2019/12/18
" Last Modified: 2022/08/31 16:25
"
"======================================================================

" vim: set noet fenc=utf-8 ff=unix sts=4 sw=4 ts=4 :


"======================================================================
" core routines
"======================================================================

"----------------------------------------------------------------------
" global variables
"----------------------------------------------------------------------
let g:quickui#core#has_nvim = has('nvim')
let g:quickui#core#has_vim9 = v:version >= 900
let g:quickui#core#has_popup = exists('*popup_create') && v:version >= 800
let g:quickui#core#has_floating = has('nvim-0.4')
let g:quickui#core#has_nvim_040 = has('nvim-0.4')
let g:quickui#core#has_nvim_050 = has('nvim-0.5.0')
let g:quickui#core#has_nvim_060 = has('nvim-0.6.0')
let g:quickui#core#has_vim_820 = (has('nvim') == 0 && has('patch-8.2.1'))
let g:quickui#core#has_win_exe = exists('*win_execute')
let g:quickui#core#has_vim9script = (v:version >= 900) && has('vim9script')


"----------------------------------------------------------------------
" internal variables
"----------------------------------------------------------------------
let s:windows = has('win32') || has('win16') || has('win64') || has('win95')


"----------------------------------------------------------------------
" object pool acquire
"----------------------------------------------------------------------
function! quickui#core#object_acquire(name)
    if !exists('g:quickui#core#__object_pool__')
        let g:quickui#core#__object_pool__ = {}
    endif
    if !has_key(g:quickui#core#__object_pool__, a:name)
        let g:quickui#core#__object_pool__[a:name] = []
    endif
    let array = g:quickui#core#__object_pool__[a:name]
    if len(array) == 0
        return v:null
    endif
    let obj = remove(array, -1)
    return obj
endfunc


"----------------------------------------------------------------------
" object pool release
"----------------------------------------------------------------------
function! quickui#core#object_release(name, obj)
    if !exists('g:quickui#core#__object_pool__')
        let g:quickui#core#__object_pool__ = {}
    endif
    if !has_key(g:quickui#core#__object_pool__, a:name)
        let g:quickui#core#__object_pool__[a:name] = []
    endif
    call add(g:quickui#core#__object_pool__[a:name], a:obj)
endfunc


"----------------------------------------------------------------------
" replace string
"----------------------------------------------------------------------
function! quickui#core#string_replace(text, old, new)
    let l:data = split(a:text, a:old, 1)
    return join(l:data, a:new)
endfunc


"----------------------------------------------------------------------
" compose two string
"----------------------------------------------------------------------
function! quickui#core#string_compose(target, pos, source)
    if a:source == ''
        return a:target
    endif
    let pos = a:pos
    let source = a:source
    if pos < 0
        let source = strcharpart(a:source, -pos)
        let pos = 0
    endif
    let target = strcharpart(a:target, 0, pos)
    if strchars(target) < pos
        let target .= repeat(' ', pos - strchars(target))
    endif
    let target .= source
    let target .= strcharpart(a:target, pos + strchars(source))
    return target
endfunc


"----------------------------------------------------------------------
" fit size
"----------------------------------------------------------------------
function! quickui#core#string_fit(source, size)
    let require = a:size
    let source = a:source
    let size = len(source)
    if size <= require
        return source
    endif
    if require <= 2
        return repeat('.', (require < 0)? 0 : require)
    endif
    let avail = require - 2
    let left = avail / 2
    let right = avail - left
    let p1 = strpart(source, 0, left)
    let p2 = strpart(source, size - right)
    let text = p1 . '..' . p2
    return text
endfunc


"----------------------------------------------------------------------
" eval & expand: '%{script}' in string
"----------------------------------------------------------------------
function! quickui#core#expand_text(string) abort
    let partial = []
    let index = 0
    while 1
        let pos = stridx(a:string, '%{', index)
        if pos < 0
            let partial += [strpart(a:string, index)]
            break
        endif
        let head = ''
        if pos > index
            let partial += [strpart(a:string, index, pos - index)]
        endif
        let endup = stridx(a:string, '}', pos + 2)
        if endup < 0
            let partial += [strpart(a:stirng, index)]
            break
        endif
        let index = endup + 1
        if endup > pos + 2
            let script = strpart(a:string, pos + 2, endup - (pos + 2))
            let script = substitute(script, '^\s*\(.\{-}\)\s*$', '\1', '')
            let result = eval(script)
            let partial += [result]
        endif
    endwhile
    return join(partial, '')
endfunc


"----------------------------------------------------------------------
" escape key character (starts by &) from string
"----------------------------------------------------------------------
function! quickui#core#escape(text)
    let text = a:text
    let rest = ''
    let start = 0
    let obj = ['', '', -1, -1, -1]
    while 1
        let pos = stridx(text, '&', start)
        if pos < 0
            let rest .= strpart(text, start)
            break
        end
        let rest .= strpart(text, start, pos - start)
        let key = strpart(text, pos + 1, 1)
        let start = pos + 2
        if key == '&'
            let rest .= '&'
        elseif key == '~'
            let rest .= '~'
        else
            let obj[1] = key
            let obj[2] = strlen(rest)
            let obj[3] = strchars(rest)
            let obj[4] = strdisplaywidth(rest)
            let rest .= key
        endif
    endwhile
    let obj[0] = rest
    return obj
endfunc


"----------------------------------------------------------------------
" list parse
"----------------------------------------------------------------------
function! quickui#core#single_parse(description)
    let item = { 'part': [], 'size': 0 }
    let item.key_char = ''
    let item.key_pos = -1
    let item.key_idx = -1
    if type(a:description) == v:t_string
        let text = a:description
        let item.cmd = ''
    elseif type(a:description) == v:t_list
        let size = len(a:description)
        let text = (size > 0)? a:description[0] : ''
        let item.cmd = (size > 1)? a:description[1] : ''
    endif
    for text in split(text, "\t")
        let obj = quickui#core#escape(text)
        let item.part += [obj[0]]
        if obj[2] >= 0 && item.key_idx < 0
            let item.key_char = obj[1]
            let item.key_pos = obj[4]
            let item.key_idx = item.size
        endif
        let item.size += 1
    endfor
    return item
endfunc


"----------------------------------------------------------------------
" tabpage instance
"----------------------------------------------------------------------
function! quickui#core#instance(local)
    let local = a:local
    if local != 0
        if exists('t:__quickui__')
            return t:__quickui__
        endif
        let t:__quickui__ = {}
        return t:__quickui__
    else
        if exists('g:__quickui__')
            return g:__quickui__
        endif
        let g:__quickui__ = {}
        return g:__quickui__
    endif
endfunc


"----------------------------------------------------------------------
" buffer instance
"----------------------------------------------------------------------
function! quickui#core#object(bid)
    let name = '__quickui__'
    let bid = (a:bid > 0)? a:bid : (bufnr())
    if bufexists(bid) == 0
        return v:null
    endif
    let obj = getbufvar(bid, name)
    if type(obj) != v:t_dict
        call setbufvar(bid, name, {})
        let obj = getbufvar(bid, name)
    endif
    return obj
endfunc


"----------------------------------------------------------------------
" object cache: acquire
"----------------------------------------------------------------------
function! quickui#core#popup_alloc(name)
    let inst = quickui#core#instance(1)
    if !has_key(inst, 'popup_cache')
        let inst.popup_cache = {}
    endif
    if !has_key(inst.popup_cache, a:name)
        let inst.popup_cache[a:name] = []
    endif
    if !empty(inst.popup_cache[a:name])
        let winid = remove(inst.popup_cache[a:name], -1)
        return winid
    endif
    let opts = {"line":1, "col":1, "wrap":0, "pos": 'topleft'}
    let winid = popup_create([], opts)
    call popup_hide(winid)
    call win_execute(winid, 'setlocal nonumber nowrap signcolumn=no')
    call setwinvar(winid, '&wincolor', 'QuickBG')
    return winid
endfunc


"----------------------------------------------------------------------
" object cache: release
"----------------------------------------------------------------------
function! quickui#core#popup_release(name, winid)
    let inst = quickui#core#instance(1)
    if !has_key(inst, 'popup_cache')
        let inst.popup_cache = {}
    endif
    if !has_key(inst.popup_cache, a:name)
        let inst.popup_cache[a:name] = []
    endif
    silent! call popup_hide(a:winid)
    let size = len(inst.popup_cache[a:name])
    call insert(inst.popup_cache[a:name], a:winid, size)
endfunc


"----------------------------------------------------------------------
" local object
"----------------------------------------------------------------------
function! quickui#core#popup_local(winid)
    let inst = quickui#core#instance(0)
    if !has_key(inst, 'popup_local')
        let inst.popup_local = {}
    endif
    if !has_key(inst.popup_local, a:winid)
        let inst.popup_local[a:winid] = {}
    endif
    return inst.popup_local[a:winid]
endfunc


"----------------------------------------------------------------------
" erase local data
"----------------------------------------------------------------------
function! quickui#core#popup_clear(winid)
    let inst = quickui#core#instance(0)
    if !has_key(inst, 'popup_local')
        let inst.popup_local = {}
    endif
    if has_key(inst.popup_local, a:winid)
        call remove(inst.popup_local, a:winid)
    endif
endfunc


"----------------------------------------------------------------------
" vim/nvim compatible
"----------------------------------------------------------------------
function! quickui#core#win_execute(winid, command, ...)
    let silent = (a:0 < 1)? 0 : (a:1)
    if g:quickui#core#has_popup != 0
        if type(a:command) == v:t_string
            keepalt call win_execute(a:winid, a:command, silent)
        elseif type(a:command) == v:t_list
            keepalt call win_execute(a:winid, join(a:command, "\n"), silent)
        endif
    elseif g:quickui#core#has_win_exe == 0
        let current = nvim_get_current_win()
        keepalt call nvim_set_current_win(a:winid)
        if type(a:command) == v:t_string
            if silent == 0
                exec a:command
            else
                silent exec a:command
            endif
        elseif type(a:command) == v:t_list
            if silent == 0
                exec join(a:command, "\n")
            else
                silent exec join(a:command, "\n")
            endif
        endif
        keepalt call nvim_set_current_win(current)
    else
        if type(a:command) == v:t_string
            keepalt call win_execute(a:winid, a:command, silent)
        elseif type(a:command) == v:t_list
            keepalt call win_execute(a:winid, join(a:command, "\n"), silent)
        endif
    endif
endfunc


"----------------------------------------------------------------------
" close window
"----------------------------------------------------------------------
function! quickui#core#win_close(winid, force)
    let [tnr, wnr] = win_id2tabwin(a:winid)
    if tnr <= 0 || wnr <= 0
        return -1
    endif
    if g:quickui#core#has_nvim == 0
        let cmd = 'close' . ((a:force != 0)? '!' : '')
        call quickui#core#win_execute(a:winid, cmd)
    else
        call nvim_win_close(a:winid, a:force)
    endif
    return 0
endfunc


"----------------------------------------------------------------------
" alloc a new buffer
"----------------------------------------------------------------------
function! quickui#core#buffer_alloc()
    if !exists('s:buffer_array')
        let s:buffer_array = {}
    endif
    let index = len(s:buffer_array) - 1
    if index >= 0
        let bid = s:buffer_array[index]
        unlet s:buffer_array[index]
    else
        if g:quickui#core#has_nvim == 0
            let bid = bufadd('')
            call bufload(bid)
            call setbufvar(bid, '&buflisted', 0)
            call setbufvar(bid, '&bufhidden', 'hide')
            call setbufvar(bid, '&buftype', 'nofile')
            call setbufvar(bid, 'noswapfile', 1)
        else
            let bid = nvim_create_buf(v:false, v:true)
            call setbufvar(bid, '&buftype', 'nofile')
            call setbufvar(bid, '&bufhidden', 'hide')
            call setbufvar(bid, 'noswapfile', 1)
        endif
    endif
    call setbufvar(bid, '&modifiable', 1)
    call deletebufline(bid, 1, '$')
    call setbufvar(bid, '&modified', 0)
    call setbufvar(bid, '&filetype', '')
    return bid
endfunc


"----------------------------------------------------------------------
" free a buffer
"----------------------------------------------------------------------
function! quickui#core#buffer_free(bid)
    if !exists('s:buffer_array')
        let s:buffer_array = {}
    endif
    let index = len(s:buffer_array)
    let s:buffer_array[index] = a:bid
    call setbufvar(a:bid, '&modifiable', 1)
    call deletebufline(a:bid, 1, '$')
    call setbufvar(a:bid, '&modified', 0)
endfunc


"----------------------------------------------------------------------
" update content
"----------------------------------------------------------------------
function! quickui#core#buffer_update(bid, textlist)
    if type(a:textlist) == v:t_list
        let textlist = a:textlist
    else
        let textlist = split('' . a:textlist, '\n', 1)
    endif
    call setbufvar(a:bid, '&modifiable', 1)
    call deletebufline(a:bid, 1, '$')
    call setbufline(a:bid, 1, textlist)
    call setbufvar(a:bid, '&modified', 0)
endfunc


"----------------------------------------------------------------------
" clear content
"----------------------------------------------------------------------
function! quickui#core#buffer_clear(bid)
    call quickui#core#buffer_update(a:bid, [])
endfunc


"----------------------------------------------------------------------
" get a named buffer
"----------------------------------------------------------------------
function! quickui#core#scratch_buffer(name, textlist)
    if !exists('s:buffer_cache')
        let s:buffer_cache = {}
    endif
    if a:name != ''
        let bid = get(s:buffer_cache, a:name, -1)
    else
        let bid = -1
    endif
    if bid < 0
        let bid = quickui#core#buffer_alloc()
        if a:name != ''
            let s:buffer_cache[a:name] = bid
        endif
    endif
    call quickui#core#buffer_update(bid, a:textlist)
    call setbufvar(bid, 'current_syntax', '')
    return bid
endfunc


"----------------------------------------------------------------------
" dummy filter
"----------------------------------------------------------------------
function! quickui#core#mock_function(id, text)
    return 0
endfunc


"----------------------------------------------------------------------
" highlight region
"----------------------------------------------------------------------
function! quickui#core#high_region(name, srow, scol, erow, ecol, virtual)
    let sep = (a:virtual == 0)? 'c' : 'v'
    let cmd = 'syn region ' . a:name . ' '
    let cmd .= ' start=/\%' . a:srow . 'l\%' . a:scol . sep . '/'
    let cmd .= ' end=/\%' . a:erow . 'l\%' . a:ecol . sep . '/'
    return cmd
endfunc


"----------------------------------------------------------------------
" patterns
"----------------------------------------------------------------------
function! quickui#core#border_extract(pattern)
    let parts = ['', '', '', '', '', '', '', '', '', '', '']
    for idx in range(11)
        let parts[idx] = strcharpart(a:pattern, idx, 1)
    endfor
    return parts
endfunc


function! quickui#core#border_convert(pattern, nvim_format)
    if type(a:pattern) == v:t_string
        let p = quickui#core#border_extract(a:pattern)
    else
        let p = a:pattern
    endif
    if len(p) == 0
        return []
    endif
    if a:nvim_format == 0
        let pattern = [ p[1], p[5], p[7], p[3], p[0], p[2], p[8], p[6] ]
    else
        let pattern = [ p[0], p[1], p[2], p[5], p[8], p[7], p[6], p[3] ]
    endif
    return pattern
endfunc

let s:border_styles = {}

let s:border_styles[0] = quickui#core#border_extract('           ')
let s:border_styles[1] = quickui#core#border_extract('+-+|-|+-+++')
let s:border_styles[2] = quickui#core#border_extract('┌─┐│─│└─┘├┤')
let s:border_styles[3] = quickui#core#border_extract('╔═╗║─║╚═╝╟╢')
let s:border_styles[4] = quickui#core#border_extract('╭─╮│─│╰─╯├┤')
let s:border_styles[5] = quickui#core#border_extract('/-\|-|\-/++')

let s:border_ascii = quickui#core#border_extract('+-+|-|+-+++')

let s:border_styles['none'] = []
let s:border_styles['single'] = s:border_styles[2]
let s:border_styles['double'] = s:border_styles[3]
let s:border_styles['rounded'] = s:border_styles[4]
let s:border_styles['solid'] = s:border_styles[0]
let s:border_styles['ascii'] = s:border_styles[1]
let s:border_styles['default'] = s:border_styles[1]

function! quickui#core#border_install(name, pattern)
    let s:border_styles[a:name] = quickui#core#border_extract(a:pattern)
endfunc

function! quickui#core#border_get(name)
    if has_key(s:border_styles, a:name)
        return s:border_styles[a:name]
    endif
    return s:border_ascii
endfunc

function! quickui#core#border_vim(name)
    let border = quickui#core#border_get(a:name)
    return quickui#core#border_convert(border, 0)
endfunc

function! quickui#core#border_nvim(name)
    let border = quickui#core#border_get(a:name)
    return quickui#core#border_convert(border, 1)
endfunc

function! quickui#core#border_auto(name)
    if g:quickui#core#has_nvim == 0
        return quickui#core#border_vim(a:name)
    else
        return quickui#core#border_nvim(a:name)
    endif
endfunc


"----------------------------------------------------------------------
" returns cursor position for screen coordination
"----------------------------------------------------------------------
function! quickui#core#cursor_pos()
    let pos = win_screenpos('.')
    return [pos[0] + winline() - 1, pos[1] + wincol() - 1]
endfunc


"----------------------------------------------------------------------
" screen boundary check, returns 1 for in screen, 0 for exceeding
"----------------------------------------------------------------------
function! quickui#core#in_screen(line, column, width, height)
    let x = a:column - 1
    let y = a:line - 1
    let w = a:width
    let h = a:height
    let screenw = &columns
    let screenh = &lines
    return (x >= 0 && y >= 0 && x + w <= screenw && y + h <= screenh)? 1 : 0
endfunc


"----------------------------------------------------------------------
" window fit screen
"----------------------------------------------------------------------
function! quickui#core#screen_fit(line, column, width, height)
    let x = a:column - 1
    let y = a:line - 1
    let w = a:width
    let h = a:height
    let screenw = &columns
    let screenh = &lines
    let x = (x + w > screenw)? screenw - w : x
    let y = (y + h > screenh)? screenh - h : y
    let x = (x < 0)? 0 : x
    let y = (y < 0)? 0 : y
    return [y + 1, x + 1]
endfunc


"----------------------------------------------------------------------
" fit screen
"----------------------------------------------------------------------
function! quickui#core#around_cursor(width, height)
    let cursor_pos = quickui#core#cursor_pos()
    let row = cursor_pos[0] + 1
    let col = cursor_pos[1] + 1
    if quickui#core#in_screen(row, col, a:width, a:height)
        return [row, col]
    endif
    if col + a:width - 1 > &columns
        let col = col - (1 + a:width)
        if quickui#core#in_screen(row, col, a:width, a:height)
            return [row, col]
        endif
    endif
    if row + a:height - 1 > &lines
        let row = row - (1 + a:height)
        if quickui#core#in_screen(row, col, a:width, a:height)
            return [row, col]
        endif
    endif
    if cursor_pos[0] + a:height + 2 < &lines
        let row = cursor_pos[0] + 1
    else
        let row = cursor_pos[0] - a:height
    endif
    if cursor_pos[1] + a:width + 2 < &columns
        let col = cursor_pos[1] + 1
    else
        let col = cursor_pos[1] - a:width
    endif
    return quickui#core#screen_fit(row, col, a:width, a:height)
endfunc


"----------------------------------------------------------------------
" safe input
"----------------------------------------------------------------------
function! quickui#core#input(prompt, text)
    call inputsave()
    try
        let t = input(a:prompt, a:text)
    catch /^Vim:Interrupt$/
        let t = "\<c-c>"
    endtry
    call inputrestore()
    return t
endfunc


"----------------------------------------------------------------------
" safe change dir
"----------------------------------------------------------------------
function! quickui#core#chdir(path)
    if has('nvim')
        let cmd = haslocaldir()? 'lcd' : (haslocaldir(-1, 0)? 'tcd' : 'cd')
    else
        let cmd = haslocaldir()? ((haslocaldir() == 1)? 'lcd' : 'tcd') : 'cd'
    endif
    silent execute cmd . ' '. fnameescape(a:path)
endfunc


"----------------------------------------------------------------------
" full file name
"----------------------------------------------------------------------
function! quickui#core#fullname(f)
    let f = a:f
    if f =~ "'."
        try
            redir => m
            silent exe ':marks' f[1]
            redir END
            let f = split(split(m, '\n')[-1])[-1]
            let f = filereadable(f)? f : ''
        catch
            let f = '%'
        endtry
    endif
    if f == '%'
        let f = expand('%')
        if &bt == 'terminal' || &bt == 'nofile'
            let f = ''
        endif
    endif
    let f = fnamemodify(f, ':p')
    if s:windows
        let f = substitute(f, "\\", '/', 'g')
    endif
    if f =~ '\/$'
        let f = fnamemodify(f, ':h')
    endif
    return f
endfunc


"----------------------------------------------------------------------
" returns nearest parent directory contains one of the markers
"----------------------------------------------------------------------
function! quickui#core#find_root(name, markers, strict)
    let name = fnamemodify((a:name != '')? a:name : bufname('%'), ':p')
    let finding = ''
    " iterate all markers
    for marker in a:markers
        if marker != ''
            " search as a file
            let x = findfile(marker, name . '/;')
            let x = (x == '')? '' : fnamemodify(x, ':p:h')
            " search as a directory
            let y = finddir(marker, name . '/;')
            let y = (y == '')? '' : fnamemodify(y, ':p:h:h')
            " which one is the nearest directory ?
            let z = (strchars(x) > strchars(y))? x : y
            " keep the nearest one in finding
            let finding = (strchars(z) > strchars(finding))? z : finding
        endif
    endfor
    if finding == ''
        let path = (a:strict == 0)? fnamemodify(name, ':h') : ''
    else
        let path = fnamemodify(finding, ':p')
    endif
    if has('win32') || has('win16') || has('win64') || has('win95')
        let path = substitute(path, '\/', '\', 'g')
    endif
    if path =~ '[\/\\]$'
        let path = fnamemodify(path, ':h')
    endif
    return path
endfunc


"----------------------------------------------------------------------
" find project root
"----------------------------------------------------------------------
function! quickui#core#project_root(name, ...)
    let markers = ['.project', '.git', '.hg', '.svn', '.root']
    if exists('g:quickui_rootmarks')
        let markers = g:quickui_rootmarks
    elseif exists('g:asyncrun_rootmarks')
        let markers = g:asyncrun_rootmarks
    endif
    let path = quickui#core#fullname(a:name)
    let strict = (a:0 > 0)? (a:1) : 0
    return quickui#core#find_root(path, markers, strict)
endfunc


"----------------------------------------------------------------------
" expand macros
"----------------------------------------------------------------------
function! quickui#core#expand_macros()
    let macros = {}
    let macros['VIM_FILEPATH'] = expand("%:p")
    let macros['VIM_FILENAME'] = expand("%:t")
    let macros['VIM_FILEDIR'] = expand("%:p:h")
    let macros['VIM_FILENOEXT'] = expand("%:t:r")
    let macros['VIM_PATHNOEXT'] = expand("%:p:r")
    let macros['VIM_FILEEXT'] = "." . expand("%:e")
    let macros['VIM_FILETYPE'] = (&filetype)
    let macros['VIM_CWD'] = getcwd()
    let macros['VIM_RELDIR'] = expand("%:h:.")
    let macros['VIM_RELNAME'] = expand("%:p:.")
    let macros['VIM_CWORD'] = expand("<cword>")
    let macros['VIM_CFILE'] = expand("<cfile>")
    let macros['VIM_CLINE'] = line('.')
    let macros['VIM_VERSION'] = ''.v:version
    let macros['VIM_SVRNAME'] = v:servername
    let macros['VIM_COLUMNS'] = ''.&columns
    let macros['VIM_LINES'] = ''.&lines
    let macros['VIM_GUI'] = has('gui_running')? 1 : 0
    let macros['VIM_ROOT'] = quickui#core#project_root('%', 0)
    let macros['VIM_HOME'] = expand(split(&rtp, ',')[0])
    let macros['VIM_PRONAME'] = fnamemodify(macros['VIM_ROOT'], ':t')
    let macros['VIM_DIRNAME'] = fnamemodify(macros['VIM_CWD'], ':t')
    let macros['<cwd>'] = macros['VIM_CWD']
    let macros['<root>'] = macros['VIM_ROOT']
    if expand("%:e") == ''
        let macros['VIM_FILEEXT'] = ''
    endif
    return macros
endfunc


"----------------------------------------------------------------------
" write script to a file and return filename
"----------------------------------------------------------------------
function! quickui#core#write_script(command, pause)
    let tmpname = fnamemodify(tempname(), ':h') . '\quickui1.cmd'
    let command = a:command
    if s:windows != 0
        let lines = ["@echo off\r"]
        let $VIM_COMMAND = a:command
        let $VIM_PAUSE = (a:pause)? 'pause' : ''
        let lines += ["call %VIM_COMMAND% \r"]
        let lines += ["set VIM_EXITCODE=%ERRORLEVEL%\r"]
        let lines += ["call %VIM_PAUSE% \r"]
        let lines += ["exit %VIM_EXITCODE%\r"]
    else
        let shell = split(&shell, ' ', 1)[0]
        let lines = ['#! ' . shell]
        let lines += [command]
        if a:pause != 0
            if executable('bash')
                let pause = 'read -n1 -rsp "press any key to continue ..."'
                let lines += ['bash -c ''' . pause . '''']
            else
                let lines += ['echo "press enter to continue ..."']
                let lines += ['sh -c "read _tmp_"']
            endif
        endif
        let tmpname = fnamemodify(tempname(), ':h') . '/quickui1.sh'
    endif
    call writefile(lines, tmpname)
    if s:windows == 0
        if exists('*setfperm')
            silent! call setfperm(tmpname, 'rwxrwxrws')
        endif
    endif
    return tmpname
endfunc


"----------------------------------------------------------------------
" string replace
"----------------------------------------------------------------------
function! quickui#core#string_replace(text, old, new)
    let data = split(a:text, a:old, 1)
    return join(data, a:new)
endfunc


"----------------------------------------------------------------------
" string strip
"----------------------------------------------------------------------
function! quickui#core#string_strip(text)
    return substitute(a:text, '^\s*\(.\{-}\)[\t\r\n ]*$', '\1', '')
endfunc



"----------------------------------------------------------------------
" extract opts+command
"----------------------------------------------------------------------
function! quickui#core#extract_opts(command)
    let cmd = substitute(a:command, '^\s*\(.\{-}\)[\s\r\n]*$', '\1', '')
    let opts = {}
    while cmd =~# '^-\%(\w\+\)\%([= ]\|$\)'
        let opt = matchstr(cmd, '^-\zs\w\+')
        if cmd =~ '^-\w\+='
            let val = matchstr(cmd, '^-\w\+=\zs\%(\\.\|\S\)*')
        else
            let val = ''
        endif
        let opts[opt] = substitute(val, '\\\(\s\)', '\1', 'g')
        let cmd = substitute(cmd, '^-\w\+\%(=\%(\\.\|\S\)*\)\=\s*', '', '')
    endwhile
    let cmd = substitute(cmd, '^\s*\(.\{-}\)\s*$', '\1', '')
    let cmd = substitute(cmd, '^@\s*', '', '')
    return [cmd, opts]
endfunc


"----------------------------------------------------------------------
" split cmdline to argv
"----------------------------------------------------------------------
function! quickui#core#split_argv(cmdline)
    let cmd = quickui#core#string_strip(a:cmdline)
    let argv = []
    while cmd =~# '^\%(\\.\|\S\)\+'
        let arg = matchstr(cmd, '^\%(\\.\|\S\)\+')
        let cmd = substitute(cmd, '^\%(\\.\|\S\)\+\s*', '', '')
        let val = substitute(arg, '\\\(\s\)', '\1', 'g')
        let argv += [val]
    endwhile
    return argv
endfunc


"----------------------------------------------------------------------
" execute string
"----------------------------------------------------------------------
function! quickui#core#execute_string(text)
    let cmd = a:text
    if cmd =~ '^[a-zA-Z0-9_#]\+(.*)$'
        exec 'call ' . cmd
    elseif cmd =~ '^<key>'
        let keys = strpart(cmd, 5)
        call feedkeys(keys)
    elseif cmd =~ '^@'
        let keys = strpart(cmd, 1)
        call feedkeys(keys)
    elseif cmd =~ '^<plug>'
        let keys = strpart(cmd, 6)
        call feedkeys("\<plug>" . keys)
    else
        exec cmd
    endif
endfunc


